using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Xml.Linq;

namespace Crap4Net.CoverageParsers
{
    public class PartCoverageParser : BaseCoverageParser 
    {
        private IEnumerable<XElement> GetAllMethods(XDocument xDoc)
        {
            return xDoc.Descendants("Method");
        }
        private string GetDllName(XElement method, XDocument xDoc)
        {
            var elem = (from assembly in xDoc.Descendants("Assembly")
                        where assembly.Attribute("id").Value == method.Parent.Attribute("asmref").Value
                        select assembly).Single();
            return elem.Attribute("name").Value;
        }
        private string GetNameSpace(XElement type)
        {
            var name = (string)type.Attribute("name");
            name = RemoveTypeName(name);
            return name;
        }
        private string[] GetSignature(XElement method)
        {
            var sigString = method.Attribute("sig").Value;
            int start = sigString.IndexOf('(');
            int end = sigString.IndexOf(')');

            sigString = sigString.Substring(start + 1, end - start - 1);
            if (String.IsNullOrEmpty(sigString))
                return new string[0];
            return sigString.Split(new char[] { ',' });
        }
        private Crap4Net.MethodSignature ParseMethodSignature(XDocument xDoc, XElement method)
        {
            string dll = GetDllName(method, xDoc);
            string nameSpace = GetNameSpace(method.Parent);
            string typeName = GetTypeNameWithoutNameSpace(method.Parent);
            string methodName = GetMethodName(method);
            string[] signature = GetSignature(method);
            var methodSignature = new MethodSignature(dll, nameSpace, typeName, methodName, signature);
            return methodSignature;
        }
        protected override void ParseDataFromXml(string reportFileName)
        {
            var xDoc = XDocument.Load(reportFileName);
            var methods = GetAllMethods(xDoc);
            foreach (var method in methods)
            {
                var methodSignature = ParseMethodSignature(xDoc, method);
                int coverage = GetCoverageValue(method);

                Data.Add(new CoverageDataEntry(methodSignature, coverage));
            }

        }

        private static string GetMethodName(XElement method)
        {
            if (IsGenericMethod(method))
            {
                return GetGenericMethodName(method);
            }
            else
            {
                return GetRegularMethodName(method);
            }
        }

        private static bool IsGenericMethod(XElement method)
        {
            var signature = (string)method.Attribute("sig");
            return signature.Contains('<');
        }

        private static string GetGenericMethodName(XElement method)
        {
            var methodName = (string)method.Attribute("name");
            var signature = (string)method.Attribute("sig");
            signature = signature.Trim(')');
            signature = signature.Trim('(');
            signature = signature.Trim();
            return methodName + signature;
        }

        private static string GetRegularMethodName(XElement method)
        {
            var methodName = (string)method.Attribute("name");
            return methodName;
        }

        private string GetTypeNameWithoutNameSpace(XElement type)
        {
            var typeName = (string)type.Attribute("name");
            typeName = RemoveNameSpace(typeName);
            return typeName;
        }

        private int GetCoverageValue(XElement method)
        {
            var actualVisits = ActualVisits(method);
            var total = TotalVisits(method);

            if (total != 0)
                return actualVisits * 100 / total;
            else
                return 0;
        }

        private string RemoveNameSpace(string name)
        {
            int index = name.IndexOf('.');
            return name.Remove(0, index + 1);
        }
        private string RemoveTypeName(string name)
        {
            int index = name.IndexOf('.');
            return name.Substring(0,index);
        }


        private int TotalVisits(XElement method)
        {
            return method.Elements("pt").Count();
        }

        private int ActualVisits(XElement method)
        {
            var result = (from visit in method.Elements("pt")
                          where (int)visit.Attribute("visit") > 0
                          select visit).Count();
            return result;
        }
    }

}
